viksoe.dk

Menu Button control

Menu Button control


This article was submitted .


This is a Menu Button control. Menu Button controls are not rocket-science, but this control is also a demonstration on how to draw an ownerdrawn button in both Windows 98 and Windows XP.

The control displays itself as a regular button, except that it has a glyph (a little arrow) on the right signalling that it displays something when you click it. All it does is to display a popup menu. The popup menu is custom drawn because I wanted to be able to add colourful icons to it. The button is also ownerdrawn because it needs to paint the dropdown-glyph on the right.

The problem with ownerdrawn buttons is that on Windows XP and greater we need to paint it with the current theme. Before Windows XP it was simply a matter of using the BS_OWNERDRAWN style. Unfortunately this also meant that we had to repaint the entire button exterior as well. Since a button always behaved the same, we could use the DrawFrameControl() API to do much of the drawing.
Along came Windows XP and changed all that. The DrawFrameControl() no longer works and we'll have to dip into the Theme API to get the job done. Well, not quite... because the old Windows Common Controls ListView and TreeView introduced a new ownerdraw method: the NM_CUSTOMDRAW notifications. And in Windows XP the Button control inherits this behaviour too (as well as supporting the old WM_DRAWITEM-range of messages).

The trick to drawing an ownerdraw button under Windows XP is not to draw the button at all.
The Button sends WM_NOTIFY / NM_CUSTOMDRAW notifications to the parent. If we wanted to drastically change the look of the button, we would start to custom paint here but since we just want to add an icon and some visual decorations, we'll answer the notification with the CDRF_NOTIFYPOSTPAINT code. This instructs Windows to paint the button entirely. When done, Windows calls us again with NM_CUSTOMDRAW and allows us to paint on top of the button image.

Call flow on Windows versions

By settings the button window text to an empty string, Windows won't paint text on the button and so we can justify the text correctly, paint it, and make room for painting the new dropdown-arrow.
Unfortunately setting the button caption to an empty string activates a bug in the Windows XP theming engine, so it doesn't generate the CDDS_POSTPAINT notification any longer. So set it to a single space character or similar.

So to properly draw an ownerdrawn button in both Windows 98 and Windows XP, we'll need to support both WM_DRAWITEM and the new NM_CUSTOMDRAW message. The only good thing about this is that the code for drawing under Windows XP is much simpler than under Windows 98.
Well, actually drawing under Windows XP is not simpler. Had I done this corrcetly I would still have needed the Theme API to draw the text in the correct font and color. The same goes for the menu background and text. The WTL library does offer a wrapper class for the theme calls, however it quickly turns into a lot of mucking about.
In this sample though, I just print the text using standard GDI calls.

How to use it

OK, back to the Menu Button control. It paints the button, but also custom paints the menu. The menu can have checkmarks (with the BMS_EX_CHECKBOXES style set in the new SetExtendedMenuStyle() method). Another style allows you to add icons to each menu item.

To use it place a button control on your dialog.
Then add a member variable to your dialog implementation file...

  CButtonMenuCtrl m_ctrlMenu
In the OnInitDialog() event handler, add the following lines:
LRESULT OnInitDialog(UINT /*uMsg*/, 
                     WPARAM /*wParam*/, 
                     LPARAM /*lParam*/, 
                     BOOL& /*bHandled*/)
{
  ...
  m_ctrlMenu.SubclassWindow(GetDlgItem(IDC_BUTTON1));
  m_ctrlMenu.SetMenu(IDM_POPUP);
  ...
}
To handle checkmarks in the menu, use the regular WTL UPDATE_UI_MAP stuff or handle WM_INITMENUPOPUP in the dialog.

Finally add the following reflection macro to your dialog's message map:

  BEGIN_MSG_MAP(CMainDlg)
    ...
    REFLECT_NOTIFICATIONS()
  END_MSG_MAP()

Source Code Dependencies

Microsoft Visual C++ 6.0
Microsoft WTL 7.5 Library

Download Files

DownloadSource Code (5 Kb)

To the top